﻿using UnityEngine;
using UnityEditor;
using System.IO;
using System;
using System.Collections.Generic;
using xuni;

namespace xres
{


    /// <summary>
    /// 资源热更
    /// </summary>
    public class ResUpdateAction : BaseAsyncAction
    {

        const int Max_Concurrent_Down_Num = 5;
        const int Max_Concurrent_Copy_Num = 5;


        public const string Err_Download = "Err_Download";
        public const string Err_Copy = "Err_Copy";

        float _lastProgress;

        //========== download

        string _baseResUrl = "http://localhost:8081/Windows/";

        int _totalDownCount = 0;
        long _totalDownBytes = 0;
        long _haveDownBytes = 0;
        readonly Queue<FileVersion> _toDownloadFiles = new Queue<FileVersion>();
        readonly List<FileVersion> _haveDownloadFiles = new List<FileVersion>();
        readonly List<FileVersion> _downloadFailFiles = new List<FileVersion>();

        /// <summary>
        /// 正在执行的download
        /// </summary>
        readonly List<DownloadFileAction> _inDownloadActionList = new List<DownloadFileAction>();
        readonly Queue<DownloadFileAction> _idleDownloadActionQueue = new Queue<DownloadFileAction>();

        //==========


        //========== copy

        readonly Queue<FileVersion> _toCopyFiles = new Queue<FileVersion>();
        readonly List<FileVersion> _haveCopyFiles = new List<FileVersion>();
        readonly List<FileVersion> _copyFailFiles = new List<FileVersion>();

        readonly List<CopyFileAction> _inCopyActionList = new List<CopyFileAction>();
        readonly Queue<CopyFileAction> _idleCopyActionQueue = new Queue<CopyFileAction>();

        //==========


        public float CheckProgress()
        {
            switch (_state)
            {
            case BaseAsyncActionState.Init:
                return 0;

            case "Check":
                return 0.01f;

            case "DownloadFiles":
            {
                var inDownBytes = 0f;
                for (var i = 0; i < _inDownloadActionList.Count; ++i)
                {
                    var act = _inDownloadActionList[i];
                    inDownBytes += act.CheckDownBytes();
                    if (false) Debug.Log($"totalDownBytes: CheckProgress: downBytes: {act.CheckDownBytes()}, total:{act._fileUrl}");
                }

                var tempBytes = inDownBytes + _haveDownBytes;
                float prg = 0.98f * tempBytes / _totalDownBytes;
                if (false) Debug.Log($"totalDownBytes: CheckProgress: prg: {prg}, inDown:{inDownBytes}, haveDown:{_haveDownBytes}, total:{_totalDownBytes}");
                prg += 0.01f;

                if (_lastProgress > prg) //下载失败时, 进度回退问题
                    return _lastProgress;

                _lastProgress = prg;
                return prg;
            }

            case "ReplaceOldFiles":
                return 0.99f;

            }

            return 1;
        }

        public void DownloadFiles(List<FileVersion> files)
        {
            switch (_state)
            {
            case BaseAsyncActionState.Init:
            case BaseAsyncActionState.Finish:
                break;

            default:
                return;
            }

            _totalDownBytes = 0;
            _haveDownBytes = 0;
            _totalDownCount = files.Count;
            for (var i = 0; i < files.Count; ++i)
            {
                var fv = files[i];
                _toDownloadFiles.Enqueue(fv);
                _totalDownBytes += fv.len;
                if (false) Debug.Log($"ResUpdate: down: len:{fv.len}, path:{fv.path}");
            }

            if (false) Debug.Log($"ResUpdate: totalDownBytes: {_totalDownBytes}");

            _lastProgress = 0;
            _state = "DownloadFiles";
            CheckToDownFile();
        }

        /// <summary>
        /// 继续把下载失败的下载完
        /// </summary>
        public void ReDownloadFailFiles()
        {
            if (BaseAsyncActionState.Finish != _state)
                return;

            if (_downloadFailFiles.Count <= 0)
                return;

            for (var i = 0; i < _downloadFailFiles.Count; ++i)
            {
                _toDownloadFiles.Enqueue(_downloadFailFiles[i]);
            }
            _downloadFailFiles.Clear();

            _lastProgress = 0;
            _state = "DownloadFiles";
            CheckToDownFile();
        }

        void CheckToDownFile()
        {
            if (_toDownloadFiles.Count <= 0 && _inDownloadActionList.Count <= 0) //所有都copy完了
            {
                if (_downloadFailFiles.Count > 0)
                {
                    SetError(Err_Download, $"some files download fail: {_downloadFailFiles.Count}");
                    Debug.LogError(ErrMsg);
                    SetFinishAndNotify(ErrMsg);
                }
                else
                {
                    ReplaceOldFiles();
                }
                return;
            }

            var sb = xuni.StringUtils.Builder;
            var diff = Max_Concurrent_Down_Num - _inDownloadActionList.Count;
            for (var i = 0; i < diff && _toDownloadFiles.Count > 0; ++i)
            {
                var fv = _toDownloadFiles.Dequeue();

                DownloadFileAction act;
                if (_idleDownloadActionQueue.Count > 0)
                    act = _idleDownloadActionQueue.Dequeue();
                else
                    act = new DownloadFileAction();
                _inDownloadActionList.Add(act); //保证在Download前执行

                act.onComplete.AddListenerWithData(OnComplete_DownloadOneFile, fv);

                var url = sb.Append(_baseResUrl).Append(fv.path).BuildString();
                act.Download(url, true, ResPath.BuildExternalTempBundlePath(fv.path), true, fv.len, fv.crc); //下载到临时目录
            }

            if (false) Debug.Log($"CheckToDownFile: toDown: {_toDownloadFiles.Count}, inDown: {_inDownloadActionList.Count}");
        }

        void OnComplete_DownloadOneFile(object obj, string type, object userData)
        {
            var downloadAction = (DownloadFileAction)obj;

            if (_idleDownloadActionQueue.Count < 5) //最多5个
                _idleDownloadActionQueue.Enqueue(downloadAction);
            _inDownloadActionList.Remove(downloadAction);

            var fv = (FileVersion)userData;

            if (!string.IsNullOrEmpty(downloadAction.ErrType)) //下载出错
            {
                _downloadFailFiles.Add(fv);
            }
            else
            {
                _haveDownloadFiles.Add(fv);
                _haveDownBytes += fv.len;
            }

            if (false) Debug.Log($"OnComplete_DownloadOneFile: haveDown:{_haveDownBytes}, total:{_totalDownBytes}, {fv.path}");
            if (false) Debug.Log($"OnComplete_DownloadOneFile: inDown: {_inDownloadActionList.Count}, hasDown: {_haveDownloadFiles.Count}, toDown: {_toDownloadFiles.Count}, {fv.path}");

            CheckToDownFile();
        }

        /// <summary>
        /// 所有文件下载完后(下载在临时文件夹的), 才把新下载文件覆盖掉旧文件
        /// </summary>
        void ReplaceOldFiles()
        {
            if (false) Debug.Log($"ReplaceOldFiles");

            for (var i = 0; i < _haveDownloadFiles.Count; ++i)
            {
                _toCopyFiles.Enqueue(_haveDownloadFiles[i]);
            }
            _haveDownloadFiles.Clear();

            _state = "ReplaceOldFiles";
            CheckToCopyFile();
        }

        /// <summary>
        /// 继续把拷贝失败的重新拷贝
        /// </summary>
        public void ReCopyFailFiles()
        {
            if (BaseAsyncActionState.Finish != _state)
                return;

            if (_copyFailFiles.Count <= 0)
                return;

            for (var i = 0; i < _copyFailFiles.Count; ++i)
            {
                _toCopyFiles.Enqueue(_copyFailFiles[i]);
            }
            _copyFailFiles.Clear();

            _state = "ReplaceOldFiles";
            CheckToCopyFile();
        }

        //多个文件同时异步copy
        void CheckToCopyFile()
        {
            if (false) Debug.Log($"CheckToCopyFile: toCopy: {_toCopyFiles.Count}, inCopy: {_inCopyActionList.Count}");

            if (_toCopyFiles.Count <= 0 && _inCopyActionList.Count <= 0) //所有都copy完了
            {
                if (_copyFailFiles.Count > 0)
                {
                    SetError(Err_Copy, $"some files copy fail: {_copyFailFiles.Count}");
                    Debug.LogError(ErrMsg);
                    SetFinishAndNotify(ErrMsg);
                }
                else
                {
                    SetError("", "");
                    SetFinishAndNotify("");
                }
                return;
            }

            var diff = Max_Concurrent_Copy_Num - _inCopyActionList.Count;
            for (var i = 0; i < diff && _toCopyFiles.Count > 0; ++i)
            {
                var fv = _toCopyFiles.Dequeue();

                CopyFileAction copyAction;
                if (_idleCopyActionQueue.Count > 0)
                    copyAction = _idleCopyActionQueue.Dequeue();
                else
                    copyAction = new CopyFileAction();

                var srcPath = ResPath.BuildExternalTempBundlePath(fv.path);
                var dstPath = ResPath.BuildExternalBundlePath(fv.path);

                copyAction.Copy(srcPath, dstPath, true);
                copyAction.onComplete.AddListenerWithData(OnComplete_CopyOneFile, fv);
                _inCopyActionList.Add(copyAction);
            }
        }

        void OnComplete_CopyOneFile(object obj, string type, object userData)
        {
            if (false) Debug.Log($"OnComplete_CopyOneFile:");

            var act = (CopyFileAction)obj;

            if (_idleCopyActionQueue.Count < 5) //最多5个
                _idleCopyActionQueue.Enqueue(act);
            _inCopyActionList.Remove(act);


            var fv = (FileVersion)userData;

            if (false) Debug.Log($"OnComplete_CopyOneFile: inCopy: {_inCopyActionList.Count}, hasCopy: {_haveCopyFiles.Count}");

            if (!string.IsNullOrEmpty(act.ErrType))
            {
                _copyFailFiles.Add(fv);
            }
            else
            {
                _haveCopyFiles.Add(fv);
            }

            CheckToCopyFile();
        }
        

    } //end of class


}

